package org.springframework.boot.loader;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.Arrays;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;

import org.springframework.boot.loader.archive.Archive;
import org.springframework.boot.loader.archive.Archive.EntryFilter;

/**
 * 
 * @author 北京八维博大科技 薛建新
 * @date 2022年11月9日 10:41:55
 * @Copyright 北京八维博大科技
 */
public class JarLauncher extends ExecutableArchiveLauncher {

	static final EntryFilter NESTED_ARCHIVE_ENTRY_FILTER = (entry) -> {
		if (entry.isDirectory()) {
			return entry.getName().equals("BOOT-INF/classes/");
		}
		return entry.getName().startsWith("BOOT-INF/lib/");
	};

	public JarLauncher() {
	}

	protected JarLauncher(Archive archive) {
		super(archive);
	}

	@Override
	protected boolean isPostProcessingClassPathArchives() {
		return false;
	}

	@Override
	protected boolean isNestedArchive(Archive.Entry entry) {
		return NESTED_ARCHIVE_ENTRY_FILTER.matches(entry);
	}

	@Override
	protected String getArchiveEntryPathPrefix() {
		return "BOOT-INF/";
	}

	public static void main(String[] args) throws Exception {
		new JarLauncher().launch(args);
	}

	@Override
	protected ClassLoader createClassLoader(URL[] urls) throws Exception {
		System.out.println("^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^");
		return createClassLoader3(urls);
	}

	private static final boolean DEBUG = true;

	private ClassLoader createClassLoader3(URL[] urls) {
		// 参数需要包的名称以 . 分割输入，看来可以直接加密jar包
		return new  LaunchedURLClassLoader (isExploded(), getArchive(), urls, getClass().getClassLoader()) {
			protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
				try {
					String className = name.replace(".", "/").concat(CLASS);
					InputStream is = super.getResourceAsStream(className);
					if (null != is) {
						// 读取魔数
						byte[] content = is.readNBytes(CLASS_MAGIC.length);
						if (isClass(content)) {
							return super.loadClass(name, resolve);
						} else {
							if (DEBUG) {
								System.out.printf("【loadClass】: name=%s, className=%s\n", name, className);
							}
							content = this.getResourceAsStream(className).readAllBytes();
							return defineClass(name, content, 0, content.length);
						}
					} else {
						return super.loadClass(name, resolve);
					}

				} catch (IOException e) {
					e.printStackTrace();
				}
				return null;
			}

			public InputStream getResourceAsStream(String name) {
				InputStream is = null;
				if (name.endsWith(CLASS)) {
					// getResourceAsStream---------------->com/xue/test/Hello.class
					try {
						byte[] content = super.getResourceAsStream(name).readAllBytes();
						if (isClass(content)) {
							// 这里可以判断魔数后解密，如果是确定魔数开始解密，否则直接返回0xCAFEBABE
							is = new ByteArrayInputStream(content);
						} else {
							if (DEBUG) {
								System.out.printf("【getResourceAsStream】: name=%s\n", name);
							}
							is = new ByteArrayInputStream(decrypt(getKey(), content));
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				} else {
					is = super.getResourceAsStream(name);
				}
				return is;
			}

		};
//		ClassLoader c2 = new MyLaunchedURLClassLoader(isExploded(), getArchive(), urls, getClass().getClassLoader());
//		return c2;
		
	}

	// 获取加密密钥，可以从环境中加载,私钥加密公钥解密 key,然后用key 解密class
	private byte[] getKey() {
		return "1234567890abcdef".getBytes();
	}

	//解密方法
	private static byte[] decrypt(byte[] key, byte[] input) {
		try {
			Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
			SecretKey keySpec = new SecretKeySpec(key, "AES");
			cipher.init(Cipher.DECRYPT_MODE, keySpec);
			return cipher.doFinal(input);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	// 总共4字节，固定为0xCAFEBABE
	private static final byte[] CLASS_MAGIC = ByteBuffer.allocate(Integer.BYTES).putInt(0xCAFEBABE).array();
	private static final String CLASS = ".class";

	// 判断是否class
	private static boolean isClass(byte[] content) {
		boolean isClass = Arrays.equals(content, 0, 4, CLASS_MAGIC, 0, 4);
		return isClass;
	}

}
